package mesweb.mesweb.service.impl.produce;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import javafx.util.Pair;
import mesweb.mesweb.dto.Procedure.ProductProduceProcedureDto;
import mesweb.mesweb.dto.ProducePlan.DayReportDetailConditionDto;
import mesweb.mesweb.dto.ProducePlan.MonthGanttDto;
import mesweb.mesweb.entity.Procedure.MesProcedure;
import mesweb.mesweb.entity.produce.ProduceOrderProductPlan;
import mesweb.mesweb.entity.produceLine.ModelProduceLineBranchLine;
import mesweb.mesweb.entity.supply.sdMaterialRequire;
import mesweb.mesweb.enums.PlanMethod;
import mesweb.mesweb.enums.enumScheduleState;
import mesweb.mesweb.mapper.procedure.MesProductProcedureBranchProcedureMapper;
import mesweb.mesweb.mapper.produce.ProduceOrderProductPlanMapper;
import mesweb.mesweb.service.produce.IProduceCalendarService;
import mesweb.mesweb.service.produce.IProduceOrderProductPlanService;
import mesweb.mesweb.service.produceLine.IModelProduceLineBranchLineService;
import mesweb.mesweb.vo.ProducePlan.GanttLink;
import mesweb.mesweb.vo.ProducePlan.GanttTask;
import mesweb.mesweb.vo.ProducePlan.MonthGantTask;
import mesweb.mesweb.vo.produceLine.ScheduleProduceLineResult;
import mesweb.mesweb.vo.produceLine.ScheduleProduceLineVo;
import mesweb.mesweb.vo.produceLine.SimpleProduceLineVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description: 订单产品排产
 * @Author: jeecg-boot
 * @Date: 2021-09-01
 * @Version: V1.0
 */
@Service
public class ProduceOrderProductPlanServiceImpl extends ServiceImpl<ProduceOrderProductPlanMapper, ProduceOrderProductPlan> implements IProduceOrderProductPlanService {

    @Autowired
    ProduceOrderProductPlanMapper mapper;

    @Autowired
    MesProductProcedureBranchProcedureMapper mesProductProcedureBranchProcedure;

    @Autowired
    IModelProduceLineBranchLineService modelProduceLineBranchLineService;

    @Autowired
    IProduceCalendarService produceCalendarService;

    //缓存以提高性能
    private Map<Long, Pair<Float, Float>> produceLineCycleTimes = new HashMap<>();


    @Override
    public IPage<ProduceOrderProductPlan> getProduceOrderProductPlanList(Page page, QueryWrapper queryWrapper) {
        return mapper.getProduceOrderProductPlanList(page, queryWrapper);
    }

    @Override
    public List<ProduceOrderProductPlan> getProduceOrderProductPlanList(QueryWrapper queryWrapper) {
        return mapper.getProduceOrderProductPlanList(queryWrapper);
    }

    @Override
    public void saveProduceOrderProductPlan(ProduceOrderProductPlan produceOrderProductPlan) {
        mapper.saveProduceOrderProductPlan(produceOrderProductPlan);
    }

    @Override
    public List<SimpleProduceLineVo> getUnSceduledProduceLines(QueryWrapper queryWrapper) {
        return mapper.getUnSceduledProduceLines(queryWrapper);
    }

    /*
     *  计算某个产品的生产最早排产期和投料提前期，根据toc约束理论的瓶颈工序排产
     * */
    @Override
    public ScheduleProduceLineResult calculateProduceScedule(ScheduleProduceLineVo scheduleProduceLineVo) {

        if (!produceLineCycleTimes.containsKey(scheduleProduceLineVo.getProduceLineId())) {
            calculateTwoCycleTime(scheduleProduceLineVo);
        }

        Pair<Float, Float> times = produceLineCycleTimes.get(scheduleProduceLineVo.getProduceLineId());
        if (null == times) return null;

        ScheduleProduceLineResult produceLineResult = new ScheduleProduceLineResult();
        produceLineResult.setPromiseProduceTime(times.getKey());

        //排产方式
        produceLineResult.setPlanMethod(scheduleProduceLineVo.getPlanMethod());
        //计算开始时间
        produceLineResult.setCalculateStartDate(scheduleProduceLineVo.getCalculateStartDate());
        //交期
        produceLineResult.setDeliveryDate(scheduleProduceLineVo.getDeliveryDate());

        if (scheduleProduceLineVo.getPlanMethod() == PlanMethod.FORWARD) {
            /**正排
             *获取当前日历,硬编码为1007. 日历怎么关联呢？
             *计划开始时间,
             */
            Date forwardPlanPlanEndDate = produceCalendarService.getDate(scheduleProduceLineVo.getCalculateStartDate(), times.getKey(), DateUnit.HOUR, 1007L);
            produceLineResult.setForwardPlanEndDate(forwardPlanPlanEndDate);
            //最早计划开始时间
            Date forwardEarliestPlanEndDate = produceCalendarService.getDate(scheduleProduceLineVo.getCalculateStartDate(), times.getValue(), DateUnit.HOUR, 1007L);
            produceLineResult.setForwardPlanEarliestEndDate(forwardEarliestPlanEndDate);

            //判断正排是否超期，超期是否认为所有的排产无效呢？
            if (DateUtil.between(produceLineResult.getDeliveryDate(), produceLineResult.getForwardPlanEndDate(), DateUnit.HOUR) > 0) {
                produceLineResult.setScheduleState(enumScheduleState.FORMARD_EXCEEDED);
            } else {
                produceLineResult.setScheduleState(enumScheduleState.FORWARD_SCHEDULED);
            }
        }
        //倒排算法:在正排不超期的情况下，所有订单依照交货期为计划结束日期整体后移即为倒排
        if (scheduleProduceLineVo.getPlanMethod().equals(PlanMethod.BACKWARD)) {
            //计划开始时间
            Date backwardPlanPlanStartDate = produceCalendarService.getDate(scheduleProduceLineVo.getCalculateStartDate(), -times.getKey(), DateUnit.HOUR, 1007L);
            //如果超期，即倒排时开始时间早于设定的计划开始计算时间则无意义(暂时按小时比较，后面设置成配置值)
            if (DateUtil.between(scheduleProduceLineVo.getCalculateStartDate(), backwardPlanPlanStartDate, DateUnit.HOUR) > 0) {
                Date backwardlatestPlanStartDate = produceCalendarService.getDate(scheduleProduceLineVo.getCalculateStartDate(), -times.getValue(), DateUnit.HOUR, 1007L);
                produceLineResult.setBackwardPlanStartDate(backwardPlanPlanStartDate);
                produceLineResult.setBackwardPlanLatestStartDate(backwardlatestPlanStartDate);
                produceLineResult.setScheduleState(enumScheduleState.BACKWARD_SCHEDULED);
            } else {
                produceLineResult.setScheduleState(enumScheduleState.BACKMARD_EXCEEDED);
            }
        }
        return produceLineResult;
    }

    @Override
    public List<SimpleProduceLineVo> getUnReverseSceduledProduceLines(QueryWrapper queryWrapper) {
        return mapper.getUnReverseSceduledProduceLines(queryWrapper);
    }

    /**
     * 获取该产线的所有工序
     *
     * @param procedureLineId 产线Id
     * @return
     */

    @Override
    public List<MesProcedure> getProcedures(Long procedureLineId) {
        ProductProduceProcedureDto productProduceProcedureDto = new ProductProduceProcedureDto();
        QueryWrapper<ModelProduceLineBranchLine> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("produce_line_id", procedureLineId);
        List<ModelProduceLineBranchLine> modelProduceLineBranchLines = modelProduceLineBranchLineService.list(queryWrapper);
        if (null == modelProduceLineBranchLines || modelProduceLineBranchLines.size() == 0) {
            return null;
        }
        productProduceProcedureDto.setProductProduceProcedureId(modelProduceLineBranchLines.get(0).getSourceLineId());
        productProduceProcedureDto.setSourceType(modelProduceLineBranchLines.get(0).getSourceLineType());
        List<MesProcedure> result = mesProductProcedureBranchProcedure.getProductProcedureProcedures(productProduceProcedureDto);
        //过滤掉null对象? 应该从sql中过滤*
        result = result.stream().filter(o -> null != o).collect(Collectors.toList());
        return result;
    }

    @Override
    public List<GanttTask> getMonthGantt(MonthGanttDto monthGanttDto) {

        return mapper.getMonthGantt(monthGanttDto);
    }

    @Override
    public MonthGantTask getMonthGanttTask(MonthGanttDto monthGanttDto) {
        MonthGantTask monthGantTask = new MonthGantTask();
        List<GanttTask> ganttTasks = mapper.getMonthGantt(monthGanttDto);
        if (null == ganttTasks || ganttTasks.size() == 0) {
            return monthGantTask;
        }
        monthGantTask.setData(ganttTasks);
        List<GanttLink> ganttLinks = new ArrayList<>();
        monthGantTask.setLinks(ganttLinks);
        for (GanttTask itm : ganttTasks
        ) {
            GanttLink ganttLink;
            for (int i = 1; i < ganttTasks.size(); i++) {
                ganttLink = new GanttLink();
                ganttLink.setId(i);
                ganttLink.setSource(Convert.toLong(itm.getId()));
                ganttLink.setTarget(Convert.toLong(ganttTasks.get(i).getId()));
                ganttLink.setType("0"); //FS
                ganttLinks.add(ganttLink);
            }
        }

        return monthGantTask;
    }

    @Override
    public IPage<sdMaterialRequire> getRequiredMaterial(Page page, DayReportDetailConditionDto dayReportDetailConditionDto) {
        return mapper.getRequiredMaterial(page, dayReportDetailConditionDto);
    }

    /**
     * 计算正常需要工时(节拍时间)和极限工时
     *
     * @param scheduleProduceLineVo
     * @return 正常生产工时和极限工时
     */
    private void calculateTwoCycleTime(ScheduleProduceLineVo scheduleProduceLineVo) {
        //获取所有工序
        List<MesProcedure> lst = getProcedures(scheduleProduceLineVo.getProduceLineId());
        if (lst == null || lst.size() == 0) {
            return;
        }
        float promiseTime = 0f;

        if (lst.size() == 1) //只有一道工序
        {
            promiseTime = lst.get(0).getStandardWorkhours() * scheduleProduceLineVo.getQty();
            produceLineCycleTimes.put(scheduleProduceLineVo.getProduceLineId(), new Pair<Float, Float>(promiseTime, promiseTime));
            return;
        }

        //按工序时长升序排列
        lst.sort(new Comparator<MesProcedure>() {
            @Override
            public int compare(MesProcedure o1, MesProcedure o2) {

                Float result = (o1.getStandardWorkhours() - o2.getStandardWorkhours());
                return result.intValue();
            }
        });

        //取最长时长工序为瓶颈工序（最后一个）
        MesProcedure bottleneck = lst.get(lst.size() - 1);

        float nonBottleNeckWorkHours = 0;
        //确定非瓶颈工序总用时
        for (MesProcedure mesProcedure : lst.subList(0, lst.size() - 2)) {
            nonBottleNeckWorkHours += mesProcedure.getStandardWorkhours();
        }
        //极限交货期=瓶颈工序时长*总数量 + 转移量*非瓶颈工序提前期
        float deliveryAheadTime = bottleneck.getStandardWorkhours() * scheduleProduceLineVo.getQty() + scheduleProduceLineVo.getTransferQty() * nonBottleNeckWorkHours;


        //交货提前期经验值
        Float cacheDeliveryTime = (bottleneck.getStandardWorkhours() * scheduleProduceLineVo.getQty() + scheduleProduceLineVo.getTransferQty() * nonBottleNeckWorkHours) * scheduleProduceLineVo.getBottleNeckCacheRatio();

        //计算瓶颈工序后用时
        Double afterBottleneck = lst.stream().filter(o -> o.getOrdered() > bottleneck.getOrdered()).mapToDouble(MesProcedure::getStandardWorkhours).sum();

        /**
         * 计算投料提前期，计算承诺计划交货时间
         * 计算公式为：
         * 假设提前期要投料的数量为x
         * 投料提前期 = x*max(瓶颈工序前的工序)+瓶颈工序用时,其中x满足 x*max(瓶颈前工序用时) = (x-1)*瓶颈工序用时
         *
         瓶颈缓冲交货期=缓冲数量×新瓶颈用时+转移批量×新非瓶颈用时
         */
        Double cacheMaterialAhead = 0d;
        Integer aheadAmount = 0;
        MesProcedure preBottleneck = lst.get(lst.size() - 2); //倒数第二个工序
        if (bottleneck.getStandardWorkhours() - preBottleneck.getStandardWorkhours() > 0) { //防止除0错误
            aheadAmount = Convert.toInt(Math.ceil(bottleneck.getStandardWorkhours() / (bottleneck.getStandardWorkhours() - preBottleneck.getStandardWorkhours())));
        }
        cacheMaterialAhead = Convert.toDouble(aheadAmount * preBottleneck.getStandardWorkhours() + bottleneck.getStandardWorkhours());
        /*
         承诺交货期=交货缓冲+瓶颈后用时+瓶颈总用时+投料提前期,物料提前缓冲期的时间里包含了
         */
        promiseTime = Convert.toFloat(deliveryAheadTime + afterBottleneck + bottleneck.getStandardWorkhours() * scheduleProduceLineVo.getQty() + cacheMaterialAhead);
        //缓存
        produceLineCycleTimes.put(scheduleProduceLineVo.getProduceLineId(), new Pair<Float, Float>(promiseTime, deliveryAheadTime));
    }

    @Override
    public List<sdMaterialRequire> getRequiredMaterial(DayReportDetailConditionDto dayReportDetailConditionDto) {
        return mapper.getRequiredMaterial(dayReportDetailConditionDto);
    }

    @Override
    public List<SimpleProduceLineVo> getSceduledProduceLines(QueryWrapper queryWrapper) {
        return mapper.getSceduledProduceLines(queryWrapper);
    }

    @Override
    public IPage<ProduceOrderProductPlan> getScheduledProduceOrderProductPlans(Page page, QueryWrapper queryWrapper) {
        return mapper.getScheduledProduceOrderProductPlans(page, queryWrapper);
    }

    @Override
    public List<ProduceOrderProductPlan> getScheduledProduceOrderProductPlans(QueryWrapper queryWrapper) {
        return mapper.getScheduledProduceOrderProductPlans(queryWrapper);
    }


}

